import java.awt.BorderLayout;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;


public class CS576_HW2_Part2_Mar_4_Yujie {
	static int width = 352;
	static int height = 288;
	public static void main(String[] args) {
		String fileName = args[0];
		int n = Integer.parseInt(args[1]);
		int m = Integer.parseInt(args[2]);
		int rError = 0;
		int gError = 0;
		int bError = 0;
		//int rgbError = 0;
		
		if(n > 16777216) 
		{
			System.out.println("Error! n should be <= 16777216.");
			System.exit(-1);
		}
		if(n < 1)
		{
			System.out.println("Error! n should > 0.");
			System.exit(-2);
		}
		
		int[][][] myRGBArrayInput = new int[width][height][3];
		int[][][] myRGBArrayRGBOutput = new int[width][height][3];
		int[][][] myRGBArrayROutput = new int[width][height][3];
		int[][][] myRGBArrayGOutput = new int[width][height][3];
		int[][][] myRGBArrayBOutput = new int[width][height][3];
		int[][][] myRGBOutputIndex = new int[width][height][3];
				
		ArrayList<Integer> myPixInputRList = new ArrayList<Integer>();
		ArrayList<Integer> myPixInputGList = new ArrayList<Integer>();
		ArrayList<Integer> myPixInputBList = new ArrayList<Integer>();
		ArrayList<Integer> myIntervalMarkR = new ArrayList<Integer>();
		ArrayList<Integer> myIntervalMarkG = new ArrayList<Integer>();
		ArrayList<Integer> myIntervalMarkB = new ArrayList<Integer>();
		
		BufferedImage myImgInput = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		BufferedImage myImgRGBOutput = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		BufferedImage myImgROutput = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		BufferedImage myImgGOutput = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		//BufferedImage myImgGOutput = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);// (TYPE_BYTE_GRAY: r = 0, g = difference, b = 0) == (TYPE_INT_RGB: r = difference, g = difference,b = difference)
		BufferedImage myImgBOutput = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		
		try {
			File file = new File(fileName);
			InputStream is = new FileInputStream(file);
			
			long len = file.length();
			byte[] bytes = new byte[(int)len];
			
			int offset = 0;
			int numRead = 0;
			while (offset < bytes.length && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0)
			{
				offset += numRead;
			}
			
			//read input image:
			int ind = 0;
			for(int y = 0; y < height; y++)
			{
				for(int x = 0; x < width; x++)
				{
					myRGBArrayInput[x][y][0] = 0x000000ff & bytes[ind];						//r
					myRGBArrayInput[x][y][1] = 0x000000ff & bytes[ind + height * width];	//g
					myRGBArrayInput[x][y][2] = 0x000000ff & bytes[ind + height * width * 2];//b
					ind++;
				}
			}
			//display input image
			for(int y = 0; y < height; y++)
			{
				for(int x = 0; x < width; x++)
				{
					//byte a = 0;
					int pix = 0xff000000 | ((myRGBArrayInput[x][y][0] & 0xff) << 16) | ((myRGBArrayInput[x][y][1] & 0xff) << 8) | (myRGBArrayInput[x][y][2] & 0xff);
					//int pix = ((a << 24) + (r << 16) + (g << 8) + b);
					myImgInput.setRGB(x,y,pix);//Sets a pixel in this BufferedImage to the specified RGB value.
					ind++;
				}
			}
			
			//Step1. Consider the space of all color vectors.
			double radical = Math.pow(n, 1/3.0);
			int[] spaceCells = new int[3];// r = 0, g = 1, b =1
			spaceCells[0] = (int)radical;
			spaceCells[1] = (int)radical;
			spaceCells[2] = (int)radical;
			
			System.out.println("n == " + n + ", radical == " + radical);
			System.out.println("spaceCells[0] == " + spaceCells[0] + ", spaceCells[1] == " + spaceCells[1] + ", spaceCells[2] == " + spaceCells[2]);
			System.out.println("Vectors == " + spaceCells[0] * spaceCells[1] * spaceCells[2]);
			
			int i = 0;
			boolean revised = false;
			while(spaceCells[0] * spaceCells[1] * spaceCells[2] < n)
			{
				revised = true;
				if(i > 2) i = 0;
				switch(i)
				{
				case 0: spaceCells[0]++;
					break;
				case 1: spaceCells[1]++;
					break;
				case 2: spaceCells[2]++;
					break;
				default:
					break;				
				}
				i++;
			}
			
			if(revised == true)
			System.out.println("\nAfter spaceCells ++, spaceCells[0] = " + spaceCells[0] + ", spaceCells[1] = " + spaceCells[1] + ", spaceCells[2] = " + spaceCells[2]);
			System.out.println("Vectors == " + spaceCells[0] * spaceCells[1] * spaceCells[2]);
			
			if(spaceCells[0] * spaceCells[1] * spaceCells[2] > n)
			{
				spaceCells[0]--;
				System.out.println("\nAfter spaceCells[0]--, spaceCells[0] = " + spaceCells[0] + ", spaceCells[1] = " + spaceCells[1] + ", spaceCells[2] = " + spaceCells[2]);
				System.out.println("Vectors == " + spaceCells[0] * spaceCells[1] * spaceCells[2]);
			}
			
			
			//Step2. Create code book.
			int[] rgbInterval = new int[3];
			//m == 1, uniform quantization
			if(m == 1)
			{
				System.out.println("\n--------\nm == 1");
				rgbInterval[0] = 256 / spaceCells[0];//R
				rgbInterval[1] = 256 / spaceCells[1];//G
				rgbInterval[2] = 256 / spaceCells[2];//B
				
				System.out.println("\n\nRed Interval = " + rgbInterval[0]);
				System.out.println("Green Interval = " + rgbInterval[1]);
				System.out.println("Blue Interval = " + rgbInterval[2]);
				
				//Step3. Create index
				
				for(int y = 0; y < height; y++)
				{
					for(int x = 0; x < width; x++)
					{
						myRGBOutputIndex[x][y][0] = myRGBArrayInput[x][y][0] / rgbInterval[0];//R index
						myRGBOutputIndex[x][y][1] = myRGBArrayInput[x][y][1] / rgbInterval[1];//G index
						myRGBOutputIndex[x][y][2] = myRGBArrayInput[x][y][2] / rgbInterval[2];//B index
						//System.out.println("myRGBOutputIndex[x][y][0] = " + myRGBOutputIndex[x][y][0]);
						//System.out.println("myRGBOutputIndex[x][y][1] = " + myRGBOutputIndex[x][y][1]);
						//System.out.println("myRGBOutputIndex[x][y][2] = " + myRGBOutputIndex[x][y][2]);
						
					}
				}
				
				//Step4. Output the quantizated RGB images.
				
				for(int y = 0; y < height; y++)
				{
					for(int x = 0; x < width; x++)
					{
						myRGBArrayRGBOutput[x][y][0] = myRGBOutputIndex[x][y][0] * rgbInterval[0] + rgbInterval[0]/2;//R
						myRGBArrayRGBOutput[x][y][1] = myRGBOutputIndex[x][y][1] * rgbInterval[1] + rgbInterval[1]/2;//G
						myRGBArrayRGBOutput[x][y][2] = myRGBOutputIndex[x][y][2] * rgbInterval[2] + rgbInterval[2]/2;//B
						if(myRGBArrayRGBOutput[x][y][0] > 255) myRGBArrayRGBOutput[x][y][0] = 255;
						if(myRGBArrayRGBOutput[x][y][0] < 0) myRGBArrayRGBOutput[x][y][0] = 0;
						if(myRGBArrayRGBOutput[x][y][1] > 255) myRGBArrayRGBOutput[x][y][1] = 255;
						if(myRGBArrayRGBOutput[x][y][1] < 0) myRGBArrayRGBOutput[x][y][1] = 0;
						if(myRGBArrayRGBOutput[x][y][2] > 255) myRGBArrayRGBOutput[x][y][2] = 255;
						if(myRGBArrayRGBOutput[x][y][2] < 0) myRGBArrayRGBOutput[x][y][2] = 0;
						//if(myRGBArrayRGBOutput[x][y][1] > 255) System.out.println("myRGBArrayRGBOutput[x][y][1] == " + myRGBArrayRGBOutput[x][y][1]);
						//System.out.println("myRGBArrayRGBOutput[x][y][0] == " + myRGBArrayRGBOutput[x][y][0]);
						//System.out.println("myRGBArrayRGBOutput[x][y][1] == " + myRGBArrayRGBOutput[x][y][1]);
						//System.out.println("myRGBArrayRGBOutput[x][y][2] == " + myRGBArrayRGBOutput[x][y][2]);
					}
				}
			}
			
			if (m == 2)// statistical distribution quantization, same amount of vector in each cube.				
			{
				System.out.println("\n--------\nm == 2");
				for(int y = 0; y < height; y++)
				{
					for(int x = 0; x < width; x++)
					{
						myPixInputRList.add(myRGBArrayInput[x][y][0]);
						myPixInputGList.add(myRGBArrayInput[x][y][1]);
						myPixInputBList.add(myRGBArrayInput[x][y][2]);
					}
				}
				Collections.sort(myPixInputRList);
				Collections.sort(myPixInputGList);
				Collections.sort(myPixInputBList);
				//System.out.println(myPixInputRList.size());
				int indexR = 0;
				System.out.println("R Min == " + myPixInputRList.get(0) + ", R Max == " + myPixInputRList.get(myPixInputRList.size() - 1));
				for(int j = 0; j < spaceCells[0]; j++)
				{
					indexR = (width * height) / spaceCells[0] * j;
					myIntervalMarkR.add(myPixInputRList.get(indexR));					
					System.out.println("R Interval " + indexR + ", mark == " + myPixInputRList.get(indexR));
				}
				myIntervalMarkR.add(256);
				System.out.println("R Last Interval, mark == " + myIntervalMarkR.get(myIntervalMarkR.size() - 1));
				
				int indexG = 0;
				System.out.println("R Min == " + myPixInputGList.get(0) + ", R Max == " + myPixInputGList.get(myPixInputGList.size() - 1));
				for(int j = 0; j < spaceCells[1]; j++)
				{
					indexG = (width * height) / spaceCells[1] * j;
					myIntervalMarkG.add(myPixInputGList.get(indexG));					
					System.out.println("G Interval " + indexG + ", mark == " + myPixInputGList.get(indexG));
				}
				myIntervalMarkG.add(256);
				System.out.println("G Last Interval, mark == " + myIntervalMarkG.get(myIntervalMarkG.size() - 1));
				
				int indexB = 0;
				System.out.println("B Min == " + myPixInputBList.get(0) + ", B Max == " + myPixInputBList.get(myPixInputBList.size() - 1));
				for(int j = 0; j < spaceCells[2]; j++)
				{
					indexB = (width * height) / spaceCells[2] * j;
					myIntervalMarkB.add(myPixInputBList.get(indexB));					
					System.out.println("B Interval " + indexB + ", mark == " + myPixInputBList.get(indexB));
				}
				myIntervalMarkB.add(256);
				System.out.println("B Last Interval, mark == " + myIntervalMarkB.get(myIntervalMarkB.size() - 1));
				
				//Step3. Create index
				for(int y = 0; y < height; y++)//R
				{
					for(int x = 0; x < width; x++)
					{
						for(int j = 0; j < (myIntervalMarkR.size()); j++)
						{
							if(myRGBArrayInput[x][y][0] >= myIntervalMarkR.get(j));
							else
							{
								myRGBOutputIndex[x][y][0] = j - 1;
								//System.out.println(myRGBOutputIndex[x][y][0]);
								break;
							}
						}
					}
				}
				
				for(int y = 0; y < height; y++)//G
				{
					for(int x = 0; x < width; x++)
					{
						for(int j = 0; j < myIntervalMarkG.size(); j++)
						{
							if(myRGBArrayInput[x][y][1] >= myIntervalMarkG.get(j));
							else
							{
								myRGBOutputIndex[x][y][1] = j - 1;
								break;
							}
						}
					}
				}
				
				for(int y = 0; y < height; y++)
				{
					for(int x = 0; x < width; x++)
					{
						for(int j = 0; j < myIntervalMarkB.size(); j++)
						{
							if(myRGBArrayInput[x][y][2] >= myIntervalMarkB.get(j));
							else
							{
								myRGBOutputIndex[x][y][2] = j - 1;
								break;
							}
						}
					}
				}
				
				
				//Step4. Output the quantizated RGB images.
				
				for(int y = 0; y < height; y++)
				{
					for(int x = 0; x < width; x++)
					{
						myRGBArrayRGBOutput[x][y][0] = myIntervalMarkR.get(myRGBOutputIndex[x][y][0]) + (myIntervalMarkR.get(myRGBOutputIndex[x][y][0] + 1) - myIntervalMarkR.get(myRGBOutputIndex[x][y][0])) / 2;  						
						if(myRGBArrayRGBOutput[x][y][0] > 255) myRGBArrayRGBOutput[x][y][0] = 255;
						if(myRGBArrayRGBOutput[x][y][0] < 0) myRGBArrayRGBOutput[x][y][0] = 0;
						//System.out.println(myRGBArrayRGBOutput[x][y][0]);
					}
				}
				
				for(int y = 0; y < height; y++)
				{
					for(int x = 0; x < width; x++)
					{
						myRGBArrayRGBOutput[x][y][1] = myIntervalMarkG.get(myRGBOutputIndex[x][y][1]) + (myIntervalMarkG.get(myRGBOutputIndex[x][y][1] + 1) - myIntervalMarkG.get(myRGBOutputIndex[x][y][1])) / 2;  						
						if(myRGBArrayRGBOutput[x][y][1] > 255) myRGBArrayRGBOutput[x][y][1] = 255;
						if(myRGBArrayRGBOutput[x][y][1] < 0) myRGBArrayRGBOutput[x][y][1] = 0;
						//System.out.println(myRGBArrayRGBOutput[x][y][1]);
					}
				}
				
				for(int y = 0; y < height; y++)
				{
					for(int x = 0; x < width; x++)
					{
						myRGBArrayRGBOutput[x][y][2] = myIntervalMarkB.get(myRGBOutputIndex[x][y][2]) + (myIntervalMarkB.get(myRGBOutputIndex[x][y][2] + 1) - myIntervalMarkB.get(myRGBOutputIndex[x][y][2])) / 2;  						
						if(myRGBArrayRGBOutput[x][y][2] > 255) myRGBArrayRGBOutput[x][y][2] = 255;
						if(myRGBArrayRGBOutput[x][y][2] < 0) myRGBArrayRGBOutput[x][y][2] = 0;
						//System.out.println(myRGBArrayRGBOutput[x][y][2]);
					}
				}
				
			}	
			
			
			//display vector quantized RGB image			
			for(int y = 0; y < height; y++)
			{
				for(int x = 0; x < width; x++)
				{
					byte a = 0;
					int pix = ((a << 24) + (myRGBArrayRGBOutput[x][y][0] << 16) + (myRGBArrayRGBOutput[x][y][1] << 8) + myRGBArrayRGBOutput[x][y][2]);// good
					myImgRGBOutput.setRGB(x,y,pix);//Sets a pixel in this BufferedImage to the specified RGB value.					
				}
			}
			
			//display output red channel gray image.			
			for(int y = 0; y < height; y++)
			{
				for(int x = 0; x < width; x++)
				{
					myRGBArrayROutput[x][y][0] = Math.abs(myRGBArrayRGBOutput[x][y][0] - myRGBArrayInput[x][y][0]);
					//myRGBArrayROutput[x][y][0] = 50;
					myRGBArrayROutput[x][y][1] = myRGBArrayROutput[x][y][0];
					myRGBArrayROutput[x][y][2] = myRGBArrayROutput[x][y][0];
					//System.out.println("myRGBArrayROutput[x][y][0] = " + myRGBArrayROutput[x][y][0]);
					rError += myRGBArrayROutput[x][y][0];					
				}
			}
			for(int y = 0; y < height; y++)
			{
				for(int x = 0; x < width; x++)
				{
					//byte a = 0;
					int pix = 0xff000000 | ((myRGBArrayROutput[x][y][0] & 0xff) << 16) | ((myRGBArrayROutput[x][y][1] & 0xff) << 8) | (myRGBArrayROutput[x][y][2] & 0xff);//must
					myImgROutput.setRGB(x, y, pix);
				}
			}
			
			//display output green channel gray image.
			for(int y = 0; y < height; y++)
			{
				for(int x = 0; x < width; x++)
				{					
					myRGBArrayGOutput[x][y][1] = Math.abs(myRGBArrayRGBOutput[x][y][1] - myRGBArrayInput[x][y][1]);
					//myRGBArrayGOutput[x][y][1] = 32;
					myRGBArrayGOutput[x][y][0] = myRGBArrayGOutput[x][y][1];
					myRGBArrayGOutput[x][y][2] = myRGBArrayGOutput[x][y][1];
					//if(myRGBArrayGOutput[x][y][1] > 50)
					//System.out.println("myRGBArrayGOutput[x][y][1]" + myRGBArrayGOutput[x][y][1]);
					gError += myRGBArrayGOutput[x][y][1];	
				}
			}
			for(int y = 0; y < height; y++)
			{
				for(int x = 0; x < width; x++)
				{
					//byte a = 0;
					int pix = 0xff000000 | ((myRGBArrayGOutput[x][y][0] & 0xff) << 16) | ((myRGBArrayGOutput[x][y][1] & 0xff) << 8) | (myRGBArrayGOutput[x][y][2] & 0xff);//must					
					myImgGOutput.setRGB(x, y, pix);
				}
			}
			
			//display output blue channel gray image.
			for(int y = 0; y < height; y++)
			{
				for(int x = 0; x < width; x++)
				{					
					myRGBArrayBOutput[x][y][2] = Math.abs(myRGBArrayRGBOutput[x][y][2] - myRGBArrayInput[x][y][2]);
					//myRGBArrayBOutput[x][y][1] = 32;
					myRGBArrayBOutput[x][y][0] = myRGBArrayBOutput[x][y][2];
					myRGBArrayBOutput[x][y][1] = myRGBArrayBOutput[x][y][2];
					//if(myRGBArrayBOutput[x][y][2] > 50)
					//System.out.println("myRGBArrayBOutput[x][y][2]" + myRGBArrayBOutput[x][y][2]);
					bError += myRGBArrayBOutput[x][y][2];	
				}
			}
			for(int y = 0; y < height; y++)
			{
				for(int x = 0; x < width; x++)
				{
					//byte a = 0;
					int pix = 0xff000000 | ((myRGBArrayBOutput[x][y][0] & 0xff) << 16) | ((myRGBArrayBOutput[x][y][1] & 0xff) << 8) | (myRGBArrayBOutput[x][y][2] & 0xff);//must					
					myImgBOutput.setRGB(x, y, pix);
				}
			}
			
			
			
			
			is.close();
			
			System.out.println("rError = " + rError);
			System.out.println("gError = " + gError);
			System.out.println("bError = " + bError);
			System.out.println("rgbError = " + (rError + gError + bError));
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		JFrame myFrameInput = new JFrame("Input Image");
		JLabel myLabelInput = new JLabel(new ImageIcon(myImgInput));
		myFrameInput.getContentPane().add(myLabelInput, BorderLayout.CENTER);
		myFrameInput.pack();
		myFrameInput.setVisible(true);
		
		
		JFrame myFrameRGBOutput = new JFrame("RGB quantized Image");
		JLabel myLabelRGBOutput = new JLabel(new ImageIcon(myImgRGBOutput));
		myFrameRGBOutput.getContentPane().add(myLabelRGBOutput, BorderLayout.CENTER);
		myFrameRGBOutput.pack();
		//myFrameRGBOutput.setLocation(400, 0);
		myFrameRGBOutput.setVisible(true);
		
		JFrame myFrameROutput = new JFrame("R channel Gray image");
		JLabel myLabelROutput = new JLabel(new ImageIcon(myImgROutput));
		myFrameROutput.getContentPane().add(myLabelROutput, BorderLayout.CENTER);
		myFrameROutput.pack();
	    myFrameROutput.setLocation(400, 0);
	    myFrameROutput.setVisible(true);
		
	    JFrame myFrameGOutput = new JFrame("G channel Gray image");
	    JLabel myLabelGOutput = new JLabel(new ImageIcon(myImgGOutput));
	    myFrameGOutput.getContentPane().add(myLabelGOutput, BorderLayout.CENTER);
	    myFrameGOutput.pack();
	    myFrameGOutput.setLocation(800, 0);
	    myFrameGOutput.setVisible(true);
		
	    JFrame myFrameBOutput = new JFrame("B channel Gray image");
	    JLabel myLabelBOutput = new JLabel(new ImageIcon(myImgBOutput));
	    myFrameBOutput.getContentPane().add(myLabelBOutput, BorderLayout.CENTER);
	    myFrameBOutput.pack();
	    myFrameBOutput.setLocation(1200, 0);
	    myFrameBOutput.setVisible(true);
		
	}
}
